﻿using System;
using System.ComponentModel;
using System.Collections.Generic;

using SharpGL.Media3D;

namespace SharpGL.SceneGraph.Primitives.SmartAxies
{
    [TypeConverter(typeof(System.ComponentModel.ExpandableObjectConverter))]
    public class AxiesWall : INotifyPropertyChanged
    {
        #region Private Fields

        AxiesWallType type;

        private bool visible = true;
        private GLColor color;
        private bool borderVisible = true;
        private GLColor borderColor;
        private float borderWidth = 1.0f;

        private float position = float.MinValue;

        private Point3D[] verticesBuffer = null;

        Rect3D boundingRect = Rect3D.Empty;

        #endregion Private Fields

        #region  Private Methods

        private GLColor DefaultColor(AxiesWallType type)
        {
            switch (type)
            {
                case AxiesWallType.XOY:
                    return new GLColor(0.0f, 0.0f, 1.0f, 1.0f);

                case AxiesWallType.XOZ:
                    return new GLColor(0.0f, 1.0f, 0.0f, 1.0f);

                case AxiesWallType.YOZ:
                    return new GLColor(1.0f, 0.0f, 0.0f, 1.0f);

                default:
                    return new GLColor(1.0f, 1.0f, 1.0f, 1.0f);
            }
        }

        #endregion Private Methods

        #region Protected Methods

        protected Point3D[] CreateVertexBuffer(AxiesWallType type, Rect3D boundingRect)
        {
            if (boundingRect.IsEmpty)
                throw new ArgumentException("Empty bounding rectangle");

            float minPosition, maxPosition;
            switch (type)
            {
                case AxiesWallType.XOY:
                    minPosition = boundingRect.Z;
                    maxPosition = boundingRect.Z + boundingRect.SizeZ;
                    
                    break;

                case AxiesWallType.XOZ:
                    minPosition = boundingRect.Y;
                    maxPosition = boundingRect.Y + boundingRect.SizeY;
                    
                    break;

                case AxiesWallType.YOZ:
                    minPosition = boundingRect.X;
                    maxPosition = boundingRect.X + boundingRect.SizeX;
                    
                    break;

                default:
                    throw new NotImplementedException("Wrong axis wall type.");
            }

            float position = this.position;
            if (position == float.MinValue)
                position = minPosition;
            else if (position == float.MaxValue)
                position = maxPosition;

            List<Point3D> vertexBuffer = new List<Point3D>(4);
            switch (type)
            {
                case AxiesWallType.XOY:
                    vertexBuffer.Add(new Point3D(boundingRect.X, boundingRect.Y, position));
                    vertexBuffer.Add(new Point3D(boundingRect.X + boundingRect.SizeX, boundingRect.Y, position));
                    vertexBuffer.Add(new Point3D(boundingRect.X + boundingRect.SizeX, boundingRect.Y + boundingRect.SizeY, position));
                    vertexBuffer.Add(new Point3D(boundingRect.X, boundingRect.Y + boundingRect.SizeY, position));

                    break;

                case AxiesWallType.XOZ:
                    vertexBuffer.Add(new Point3D(boundingRect.X, position, boundingRect.Z));
                    vertexBuffer.Add(new Point3D(boundingRect.X + boundingRect.SizeX, position, boundingRect.Z));
                    vertexBuffer.Add(new Point3D(boundingRect.X + boundingRect.SizeX, position, boundingRect.Z + boundingRect.SizeZ));
                    vertexBuffer.Add(new Point3D(boundingRect.X, position, boundingRect.Z + boundingRect.SizeZ));
                    
                    break;

                case AxiesWallType.YOZ:
                    vertexBuffer.Add(new Point3D(position, boundingRect.Y, boundingRect.Z));
                    vertexBuffer.Add(new Point3D(position, boundingRect.Y + boundingRect.SizeY, boundingRect.Z));
                    vertexBuffer.Add(new Point3D(position, boundingRect.Y + boundingRect.SizeY, boundingRect.Z + boundingRect.SizeZ));
                    vertexBuffer.Add(new Point3D(position, boundingRect.Y, boundingRect.Z + boundingRect.SizeZ));
                    
                    break;

                default:
                    throw new NotImplementedException("Wrong axis wall type.");
            }

            return vertexBuffer.ToArray();
        }

        protected void InternalRender(OpenGL gl, Rect3D boundingRect)
        {
            if (!OpenGL.IsValidContext(gl) || boundingRect.IsEmpty)
            {
                verticesBuffer = null;

                return;
            }

            if (this.boundingRect != boundingRect)
            {
                verticesBuffer = CreateVertexBuffer(type, boundingRect);

                this.boundingRect = boundingRect;
            }

            if (visible)
            {
                gl.PushAttrib(Enumerations.AttributeMask.Current | Enumerations.AttributeMask.Line | Enumerations.AttributeMask.Enable);
                {
                    byte[] isLightingEnable = new byte[1];
                    gl.GetBooleanv(Enumerations.GetTarget.Lighting, isLightingEnable);
                    if (isLightingEnable[0] != 0)
                        gl.Enable(OpenGL.GL_COLOR_MATERIAL);

                    gl.Disable(OpenGL.GL_DEPTH_TEST);
                    gl.Enable(OpenGL.GL_BLEND);
                    gl.BlendFunc(Enumerations.BlendingSourceFactor.SourceAlpha, Enumerations.BlendingDestinationFactor.OneMinusSourceAlpha);

                    gl.Begin(Enumerations.BeginMode.TriangleFan);
                    {
                        gl.Color(color);
                        foreach (Point3D vertex in verticesBuffer)
                            gl.Vertex(vertex);
                    }
                    gl.End();

                    if (borderVisible)
                    {
                        gl.Enable(OpenGL.GL_DEPTH_TEST);

                        gl.LineWidth(borderWidth);
                        gl.Color(borderColor);

                        gl.Begin(Enumerations.BeginMode.LineLoop);
                        {
                            foreach (Point3D vertex in verticesBuffer)
                                gl.Vertex(vertex);
                        }
                        gl.End();
                    }
                }
                gl.PopAttrib();
            }
        }

        /// <summary>
        /// Call this function to do the PropertyChanged event.
        /// </summary>
        protected virtual void NotifyPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
        
        #endregion Protected Methods

        #region Public Constructors

        public AxiesWall()
            : this(AxiesWallType.XOY)
        {
        }

        public AxiesWall(AxiesWallType type)
        {
            this.type = type;

            color = borderColor = DefaultColor(type);
            color.A = 0.4f;
        }

        #endregion Public Constructors

        #region Public Methods

        public void Render(OpenGL gl, Rect3D boundingRect)
        {
            InternalRender(gl, boundingRect);
        }

        #endregion Public Methods

        #region Public Events

        [Description("Called when properties has been changed."), Category("SharpGL")]
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion Public Events

        #region Public Properties

        [Description("Type"), Category("Axies wall")]
        public AxiesWallType Type
        {
            get { return type; }
            set
            {
                if (type != value)
                {
                    type = value;

                    boundingRect = Rect3D.Empty;

                    NotifyPropertyChanged("Type");
                }
            }
        }

        [Description("Visible"), Category("Axies wall")]
        public bool Visible
        {
            get { return visible; }
            set
            {
                if (visible != value)
                {
                    visible = value;

                    NotifyPropertyChanged("Visible");
                }
            }
        }

        [Description("Visible"), Category("Axies wall")]
        public GLColor Color
        {
            get { return color; }
            set
            {
                if (color != value)
                {
                    color = value;

                    NotifyPropertyChanged("Color");
                }
            }
        }

        [Description("BorderVisible"), Category("Axies wall")]
        public bool BorderVisible
        {
            get { return borderVisible; }
            set
            {
                if (borderVisible != value)
                {
                    borderVisible = value;

                    NotifyPropertyChanged("BorderVisible");
                }
            }
        }

        [Description("BorderColor"), Category("Axies wall")]
        public GLColor BorderColor
        {
            get { return borderColor; }
            set
            {
                if (borderColor != value)
                {
                    borderColor = value;

                    NotifyPropertyChanged("BorderColor");
                }
            }
        }

        [Description("BorderWidth"), Category("Axies wall")]
        public float BorderWidth
        {
            get { return borderWidth; }
            set
            {
                if (borderWidth != value)
                {
                    borderWidth = value;

                    NotifyPropertyChanged("BorderWidth");
                }
            }
        }

        [Description("Position"), Category("Axies wall")]
        public float Position
        {
            get { return position; }
            set
            {
                if (position != value)
                {
                    position = value;

                    boundingRect = Rect3D.Empty;

                    NotifyPropertyChanged("Position");
                }
            }
        }

        #endregion Public Properties
    }
}
